#include "Decimal.h"

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <fan-llvm/core/Core.h>
#include "CastErr.h"
#include "Err.h"
#include "ParseErr.h"
#include "Str.h"
#include "Type.h"
#include "UnsupportedErr.h"


static Decimal* kZeroDecimal;
static Decimal* kOneDecimal;

Type* Decimal_classType = NULL;
VTable* Decimal_classVTable = NULL;


/**********************************************
 *
 * Methods and fields
 *
 **********************************************/


/* Bool equals(Obj? obj) */
Bool Decimal_equals(Obj self, NullableObj obj)
{
  if (!VAL_IS_NULL(obj) && Obj_typeof(obj) == Decimal_classType)
  {
    Decimal* d1 = VAL_GET_REF(self);
    Decimal* d2 = VAL_GET_REF(obj);
    return (d1->val == d2->val);
  }
  else
    return FALSE;
}


/* Int compare(Obj obj) */
Int Decimal_compare(Obj self, Obj obj)
{
  if (Obj_typeof(obj) == Decimal_classType)
  {
    Decimal* d1 = VAL_GET_REF(self);
    Decimal* d2 = VAL_GET_REF(obj);

    if (d1->val < d2->val)
      return -1;
    else if (d1->val > d2->val)
      return 1;
    else
      return 0;
  }
  else
  {
    exceptionThrow(CastErr_newWithTypes(Obj_typeof(obj), Decimal_classType));
    return 0;
  }
}


/* Int hash() */
Int Decimal_hash(Obj self)
{
  Decimal* d = VAL_GET_REF(self);
  int64_t* intPtr = (int64_t*)&d->val;
  return *intPtr;
}


/* Str toStr() */
Str* Decimal_toStr(Obj self)
{
  Decimal* d = VAL_GET_REF(self);
  char buffer[32];
  snprintf(buffer, 32, "%.12g", d->val);
  return Str_newFromCStr(buffer);
}


/* Decimal defVal */
Decimal* Decimal_defVal(void)
{
  return kZeroDecimal;
}


/* Decimal? fromStr(Str s, Bool checked) */
Decimal* Decimal_fromStr_1(Str* s)
{
  return Decimal_fromStr(s, TRUE);
}


/* Decimal? fromStr(Str s, Bool checked) */
Decimal* Decimal_fromStr(Str* s, Bool checked)
{
  char buffer[32];
  if (Str_fillCStr(s, buffer, 32))
  {
    char* endPtr = NULL;
    double val = strtod(buffer, &endPtr);
    if (endPtr > buffer && *endPtr == '\0')
      return Decimal_new(val);
  }

  if (checked)
    exceptionThrow(ParseErr_newWithFormattedMsg("Invalid Decimal: '%r'", s));

  return NULL;
}


/* Decimal negate() */
Decimal* Decimal_negate(Decimal* self)
{
  return Decimal_new(-self->val);
}


/* Decimal increment() */
Decimal* Decimal_increment(Decimal* self)
{
  return Decimal_plus(self, kOneDecimal);
}


/* Decimal decrement() */
Decimal* Decimal_decrement(Decimal* self)
{
  return Decimal_minus(self, kOneDecimal);
}


/* Decimal mult(Decimal b) */
Decimal* Decimal_mult(Decimal* self, Decimal* b)
{
  return Decimal_new(self->val * b->val);
}


/* Decimal multInt(Int b) */
Decimal* Decimal_multInt(Decimal* self, Int b)
{
  return Decimal_mult(self, Decimal_new(b));
}


/* Decimal multFloat(Float b) */
Decimal* Decimal_multFloat(Decimal* self, Float b)
{
  return Decimal_mult(self, Decimal_new(b));
}


/* Decimal div(Decimal b) */
Decimal* Decimal_div(Decimal* self, Decimal* b)
{
  if (b->val == 0.0 || b->val == -0.0)
    exceptionThrow(Err_newWithFormattedMsg("Division by zero"));
  return Decimal_new(self->val / b->val);
}


/* Decimal divInt(Int b) */
Decimal* Decimal_divInt(Decimal* self, Int b)
{
  return Decimal_div(self, Decimal_new(b));
}


/* Decimal divFloat(Float b) */
Decimal* Decimal_divFloat(Decimal* self, Float b)
{
  return Decimal_div(self, Decimal_new(b));
}


/* Decimal mod(Decimal b) */
Decimal* Decimal_mod(Decimal* self, Decimal* b)
{
  if (b->val == 0.0 || b->val == -0.0)
    exceptionThrow(Err_newWithFormattedMsg("Mod by zero"));
  return Decimal_new(fmod(self->val, b->val));
}


/* Decimal modInt(Int b) */
Decimal* Decimal_modInt(Decimal* self, Int b)
{
  return Decimal_mod(self, Decimal_new(b));
}


/* Decimal modFloat(Float b) */
Decimal* Decimal_modFloat(Decimal* self, Float b)
{
  return Decimal_mod(self, Decimal_new(b));
}


/* Decimal plus(Decimal b) */
Decimal* Decimal_plus(Decimal* self, Decimal* b)
{
  return Decimal_new(self->val + b->val);
}


/* Decimal plusInt(Int b) */
Decimal* Decimal_plusInt(Decimal* self, Int b)
{
  return Decimal_plus(self, Decimal_new(b));
}


/* Decimal plusFloat(Float b) */
Decimal* Decimal_plusFloat(Decimal* self, Float b)
{
  return Decimal_plus(self, Decimal_new(b));
}


/* Decimal minus(Decimal b) */
Decimal* Decimal_minus(Decimal* self, Decimal* b)
{
  return Decimal_new(self->val - b->val);
}


/* Decimal minusInt(Int b) */
Decimal* Decimal_minusInt(Decimal* self, Int b)
{
  return Decimal_minus(self, Decimal_new(b));
}


/* Decimal minusFloat(Float b) */
Decimal* Decimal_minusFloat(Decimal* self, Float b)
{
  return Decimal_minus(self, Decimal_new(b));
}


/* Decimal abs() */
Decimal* Decimal_abs(Decimal* self)
{
  return (self->val >= 0.0) ? self : Decimal_negate(self);
}


/* Decimal min(Decimal that) */
Decimal* Decimal_min(Decimal* self, Decimal* that)
{
  return (self->val <= that->val) ? self : that;
}


/* Decimal max(Decimal that) */
Decimal* Decimal_max(Decimal* self, Decimal* that)
{
  return (self->val >= that->val) ? self : that;
}


/* Str toCode() */
Str* Decimal_toCode(Decimal* self)
{
  char buffer[32];
  snprintf(buffer, 32, "%.12gd", self->val);
  return Str_newFromCStr(buffer);
}


/* Str toLocale(Str? pattern) */
Str* Decimal_toLocale_0(Decimal* self)
{
  return Decimal_toLocale(self, NULL);
}


/* Str toLocale(Str? pattern) */
Str* Decimal_toLocale(Decimal* self, Str* pattern)
{
  exceptionThrow(UnsupportedErr_newWithFormattedMsg("sys::Decimal.toLocale not implemented yet"));
  return NULL;
}


/**********************************************
 *
 * Extra methods
 *
 **********************************************/


/* Create a new Decimal instance with the given value */
Decimal* Decimal_new(double val)
{
  if (isinf(val) != 0 || isnan(val) != 0)
    exceptionThrow(Err_newWithFormattedMsg("Infinite or NaN"));

  Decimal* decimal = (Decimal*)Obj_allocate(sizeof(Decimal), Decimal_classVTable);
  Obj_make(VAL_MAKE_REF(decimal));
  decimal->val = val;
  return decimal;
}


/**********************************************
 *
 * Type-system functions
 *
 **********************************************/


Type* Decimal_initType(void)
{
  if (Decimal_classType == NULL)
  {
    Type* baseType = Num_initType();
    VTable* vtable = Decimal_initVTable();
    Type* type = Type_new("Decimal", baseType, vtable, sizeof(Decimal), sizeof(DecimalVTable));
    Type_registerMethodImplementation(type, "abs", Decimal_abs, 0);
    Type_registerMethodImplementation(type, "compare", Decimal_compare, 1);
    Type_registerMethodImplementation(type, "decrement", Decimal_decrement, 0);
    Type_registerMethodImplementation(type, "div", Decimal_div, 1);
    Type_registerMethodImplementation(type, "divFloat", Decimal_divFloat, 1);
    Type_registerMethodImplementation(type, "divInt", Decimal_divInt, 1);
    Type_registerMethodImplementation(type, "equals", Decimal_equals, 1);
    Type_registerMethodImplementation(type, "fromStr", Decimal_fromStr_1, 1);
    Type_registerMethodImplementation(type, "fromStr", Decimal_fromStr, 2);
    Type_registerMethodImplementation(type, "hash", Decimal_hash, 0);
    Type_registerMethodImplementation(type, "increment", Decimal_increment, 0);
    Type_registerMethodImplementation(type, "max", Decimal_max, 1);
    Type_registerMethodImplementation(type, "min", Decimal_min, 1);
    Type_registerMethodImplementation(type, "minus", Decimal_minus, 1);
    Type_registerMethodImplementation(type, "minusFloat", Decimal_minusFloat, 1);
    Type_registerMethodImplementation(type, "minusInt", Decimal_minusInt, 1);
    Type_registerMethodImplementation(type, "mod", Decimal_mod, 1);
    Type_registerMethodImplementation(type, "modFloat", Decimal_modFloat, 1);
    Type_registerMethodImplementation(type, "modInt", Decimal_modInt, 1);
    Type_registerMethodImplementation(type, "mult", Decimal_mult, 1);
    Type_registerMethodImplementation(type, "multFloat", Decimal_multFloat, 1);
    Type_registerMethodImplementation(type, "multInt", Decimal_multInt, 1);
    Type_registerMethodImplementation(type, "negate", Decimal_negate, 0);
    Type_registerMethodImplementation(type, "plus", Decimal_plus, 1);
    Type_registerMethodImplementation(type, "plusFloat", Decimal_plusFloat, 1);
    Type_registerMethodImplementation(type, "plusInt", Decimal_plusInt, 1);
    Type_registerMethodImplementation(type, "toCode", Decimal_toCode, 0);
    Type_registerMethodImplementation(type, "toLocale", Decimal_toLocale_0, 0);
    Type_registerMethodImplementation(type, "toLocale", Decimal_toLocale, 1);
    Type_registerMethodImplementation(type, "toStr", Decimal_toStr, 0);
    Type_registerFieldImplementation(type, "defVal", Decimal_defVal, NULL);
    Decimal_classType = type;
    Decimal_initStatic();
  }
  return Decimal_classType;
}


VTable* Decimal_initVTable(void)
{
  if (Decimal_classVTable == NULL)
  {
    VTable* vtable = memoryAlloc(sizeof(DecimalVTable));
    memcpy(vtable, Num_initVTable(), sizeof(FanNumVTable));
    ((FanObjVTable*)vtable)->compare = Decimal_compare;
    ((FanObjVTable*)vtable)->equals = Decimal_equals;
    ((FanObjVTable*)vtable)->hash = Decimal_hash;
    ((FanObjVTable*)vtable)->toStr = Decimal_toStr;
    Decimal_classVTable = vtable;
  }
  return Decimal_classVTable;
}


void Decimal_initStatic(void)
{
  kZeroDecimal = Decimal_new(0.0);
  kOneDecimal = Decimal_new(1.0);
}
